package expo.modules.iroh

import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import expo.modules.kotlin.Promise
import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.types.Enumerable
import kotlinx.coroutines.*
import android.content.Context
import android.content.SharedPreferences
import org.json.JSONObject
import org.json.JSONArray
import java.io.File
import java.security.MessageDigest
import java.util.*
import android.util.Base64

// MARK: - Kotlin Implementation for Iroh Integration

class IrohModule : Module() {
    override fun definition() = ModuleDefinition {
        Name("Iroh")
        
        // MARK: - Node Management
        AsyncFunction("createNode") Coroutine { config: Map<String, Any>? ->
            createIrohNode(config)
        }
        
        AsyncFunction("getNodeInfo") Coroutine {
            getNodeInfo()
        }
        
        AsyncFunction("shutdownNode") Coroutine {
            shutdownNode()
        }
        
        // MARK: - Document Operations
        AsyncFunction("createDocument") Coroutine { name: String ->
            createDocument(name)
        }
        
        AsyncFunction("getDocument") Coroutine { documentId: String ->
            getDocument(documentId)
        }
        
        AsyncFunction("listDocuments") Coroutine {
            listDocuments()
        }
        
        AsyncFunction("deleteDocument") Coroutine { documentId: String ->
            deleteDocument(documentId)
        }
        
        // MARK: - Blob Operations
        AsyncFunction("addBlob") Coroutine { data: ByteArray ->
            addBlob(data)
        }
        
        AsyncFunction("getBlob") Coroutine { hash: String ->
            getBlob(hash)
        }
        
        // MARK: - File Operations
        AsyncFunction("addFile") Coroutine { filePath: String ->
            addFile(filePath)
        }
        
        AsyncFunction("exportFile") Coroutine { hash: String, outputPath: String ->
            exportFile(hash, outputPath)
        }
        
        // MARK: - Network Operations
        AsyncFunction("connect") Coroutine { nodeId: String ->
            connectToNode(nodeId)
        }
        
        AsyncFunction("disconnect") Coroutine { nodeId: String ->
            disconnectFromNode(nodeId)
        }
        
        AsyncFunction("listConnections") Coroutine {
            listConnections()
        }
        
        // MARK: - Sharing Operations
        AsyncFunction("shareDocument") Coroutine { documentId: String ->
            shareDocument(documentId)
        }
        
        AsyncFunction("receiveDocument") Coroutine { ticket: String ->
            receiveDocument(ticket)
        }
        
        // MARK: - Events
        Events("transferProgress")
    }
    
    // MARK: - Private Properties
    private var irohNode: IrohNodeWrapper? = null
    private val context: Context
        get() = appContext.reactContext ?: throw IllegalStateException("React context is null")
    
    // MARK: - Implementation Methods
    
    private suspend fun createIrohNode(config: Map<String, Any>?): Map<String, Any> = withContext(Dispatchers.IO) {
        val nodeId = config?.get("nodeId") as? String ?: generateNodeId()
        irohNode = IrohNodeWrapper(context, nodeId)
        
        irohNode?.initialize()
        
        mapOf(
            "id" to nodeId,
            "isConnected" to true,
            "documents" to emptyList<Map<String, Any>>()
        )
    }
    
    private suspend fun getNodeInfo(): Map<String, Any> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        
        mapOf(
            "id" to node.nodeId,
            "isConnected" to node.isConnected,
            "documents" to node.listDocuments()
        )
    }
    
    private suspend fun shutdownNode(): Boolean = withContext(Dispatchers.IO) {
        val node = irohNode ?: return@withContext false
        
        node.shutdown()
        irohNode = null
        true
    }
    
    private suspend fun createDocument(name: String): Map<String, Any> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.createDocument(name)
    }
    
    private suspend fun getDocument(documentId: String): Map<String, Any>? = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.getDocument(documentId)
    }
    
    private suspend fun listDocuments(): List<Map<String, Any>> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.listDocuments()
    }
    
    private suspend fun deleteDocument(documentId: String): Boolean = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.deleteDocument(documentId)
    }
    
    private suspend fun addBlob(data: ByteArray): Map<String, Any> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.addBlob(data)
    }
    
    private suspend fun getBlob(hash: String): ByteArray? = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.getBlob(hash)
    }
    
    private suspend fun addFile(filePath: String): Map<String, Any> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        
        val file = File(filePath)
        val data = file.readBytes()
        node.addBlob(data)
    }
    
    private suspend fun exportFile(hash: String, outputPath: String): Boolean = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        
        val data = node.getBlob(hash) ?: return@withContext false
        
        val outputFile = File(outputPath)
        outputFile.writeBytes(data)
        true
    }
    
    private suspend fun connectToNode(nodeId: String): Boolean = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.connect(nodeId)
    }
    
    private suspend fun disconnectFromNode(nodeId: String): Boolean = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.disconnect(nodeId)
    }
    
    private suspend fun listConnections(): List<String> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.listConnections()
    }
    
    private suspend fun shareDocument(documentId: String): String = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        node.shareDocument(documentId)
    }
    
    private suspend fun receiveDocument(ticket: String): Map<String, Any> = withContext(Dispatchers.IO) {
        val node = irohNode ?: throw IrohException("Node not initialized")
        
        node.receiveDocument(ticket) { progress ->
            // Send progress updates to React Native
            sendEvent("transferProgress", mapOf(
                "transferred" to progress.transferred,
                "total" to progress.total,
                "percentage" to progress.percentage,
                "status" to progress.status
            ))
        }
    }
    
    // MARK: - Helper Functions
    
    private fun generateNodeId(): String {
        return "node_${System.currentTimeMillis()}_${UUID.randomUUID().toString().take(8)}"
    }
}

// MARK: - Exception Types

class IrohException(message: String) : Exception(message)

// MARK: - Data Classes

data class TransferProgress(
    val transferred: Long,
    val total: Long,
    val percentage: Double,
    val status: String
)

// MARK: - Node Wrapper Class

private class IrohNodeWrapper(
    private val context: Context,
    val nodeId: String
) {
    var isConnected: Boolean = false
        private set
    
    private val documents = mutableMapOf<String, Map<String, Any>>()
    private val blobs = mutableMapOf<String, ByteArray>()
    private val connections = mutableSetOf<String>()
    
    private val sharedPreferences: SharedPreferences by lazy {
        context.getSharedPreferences("iroh_$nodeId", Context.MODE_PRIVATE)
    }
    
    suspend fun initialize() {
        // In a real implementation, this would initialize the actual iroh node
        // using the iroh-ffi Kotlin bindings
        isConnected = true
        
        // Load persisted data
        loadPersistedData()
    }
    
    suspend fun shutdown() {
        // Persist data before shutdown
        persistData()
        isConnected = false
        connections.clear()
    }
    
    suspend fun createDocument(name: String): Map<String, Any> {
        val documentId = "doc_${System.currentTimeMillis()}_${UUID.randomUUID().toString().take(8)}"
        val currentTime = System.currentTimeMillis()
        
        val document = mapOf(
            "id" to documentId,
            "name" to name,
            "size" to 0,
            "created" to currentTime,
            "modified" to currentTime
        )
        
        documents[documentId] = document
        persistData()
        
        return document
    }
    
    suspend fun getDocument(documentId: String): Map<String, Any>? {
        return documents[documentId]
    }
    
    suspend fun listDocuments(): List<Map<String, Any>> {
        return documents.values.toList()
    }
    
    suspend fun deleteDocument(documentId: String): Boolean {
        val removed = documents.remove(documentId) != null
        if (removed) {
            persistData()
        }
        return removed
    }
    
    suspend fun addBlob(data: ByteArray): Map<String, Any> {
        val hash = calculateHash(data)
        blobs[hash] = data
        
        persistData()
        
        return mapOf(
            "hash" to hash,
            "size" to data.size,
            "format" to "raw"
        )
    }
    
    suspend fun getBlob(hash: String): ByteArray? {
        return blobs[hash]
    }
    
    suspend fun connect(nodeId: String): Boolean {
        // In a real implementation, this would establish a P2P connection
        connections.add(nodeId)
        return true
    }
    
    suspend fun disconnect(nodeId: String): Boolean {
        return connections.remove(nodeId)
    }
    
    suspend fun listConnections(): List<String> {
        return connections.toList()
    }
    
    suspend fun shareDocument(documentId: String): String {
        if (!documents.containsKey(documentId)) {
            throw IrohException("Document not found")
        }
        
        // Generate a share ticket
        val ticket = JSONObject().apply {
            put("sourceNode", nodeId)
            put("documentId", documentId)
            put("timestamp", System.currentTimeMillis())
        }
        
        return Base64.encodeToString(ticket.toString().toByteArray(), Base64.DEFAULT)
    }
    
    suspend fun receiveDocument(
        ticket: String,
        onProgress: (TransferProgress) -> Unit
    ): Map<String, Any> {
        // Parse ticket
        val ticketData = try {
            String(Base64.decode(ticket, Base64.DEFAULT))
        } catch (e: Exception) {
            throw IrohException("Invalid ticket format")
        }
        
        val ticketInfo = try {
            JSONObject(ticketData)
        } catch (e: Exception) {
            throw IrohException("Invalid ticket format")
        }
        
        val sourceNode = ticketInfo.getString("sourceNode")
        
        // Simulate transfer progress
        for (i in 0..100 step 10) {
            val progress = TransferProgress(
                transferred = i.toLong(),
                total = 100,
                percentage = i.toDouble(),
                status = if (i == 100) "completed" else "transferring"
            )
            onProgress(progress)
            
            // Small delay to simulate transfer
            delay(100)
        }
        
        // Create received document
        return createDocument("Received from $sourceNode")
    }
    
    private fun calculateHash(data: ByteArray): String {
        // Simple hash implementation for demo
        val digest = MessageDigest.getInstance("MD5")
        val hashBytes = digest.digest(data)
        val hash = hashBytes.joinToString("") { "%02x".format(it) }
        return "hash_${hash.take(16)}_${data.size}"
    }
    
    private fun loadPersistedData() {
        try {
            val documentsJson = sharedPreferences.getString("documents", null)
            if (documentsJson != null) {
                val jsonArray = JSONArray(documentsJson)
                for (i in 0 until jsonArray.length()) {
                    val docJson = jsonArray.getJSONObject(i)
                    val doc = mapOf(
                        "id" to docJson.getString("id"),
                        "name" to docJson.getString("name"),
                        "size" to docJson.getInt("size"),
                        "created" to docJson.getLong("created"),
                        "modified" to docJson.getLong("modified")
                    )
                    documents[doc["id"] as String] = doc
                }
            }
        } catch (e: Exception) {
            // Log error but continue
            println("Failed to load persisted data: ${e.message}")
        }
    }
    
    private fun persistData() {
        try {
            val jsonArray = JSONArray()
            documents.values.forEach { doc ->
                val docJson = JSONObject().apply {
                    put("id", doc["id"])
                    put("name", doc["name"])
                    put("size", doc["size"])
                    put("created", doc["created"])
                    put("modified", doc["modified"])
                }
                jsonArray.put(docJson)
            }
            
            sharedPreferences.edit()
                .putString("documents", jsonArray.toString())
                .apply()
        } catch (e: Exception) {
            // Log error but continue
            println("Failed to persist data: ${e.message}")
        }
    }
}